package com.aner.tester;

import com.aner.tester.config.AnerTesterConfig;
import com.aner.tester.constant.Constant;
import com.aner.tester.executor.CodeGenExecutor;
import com.aner.tester.executor.TaskResult;
import com.aner.tester.executor.TaskStatus;
import com.aner.tester.generator.TypeManager;
import com.aner.tester.generator.code.CodeGenerator;
import com.aner.tester.generator.data.ObjectGenerator;
import com.aner.tester.generator.data.ValueGenerator;
import com.aner.tester.object.ControllerObject;
import com.aner.tester.resolver.ControllerResolver;
import com.aner.tester.scanner.FilterFlag;
import com.aner.tester.scanner.ScannerFactory;
import com.aner.tester.utils.DateUtils;
import com.aner.tester.utils.text.StringUtils;
import org.slf4j.Logger;
import com.aner.tester.utils.LoggerUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 *
 * aner-tester executor
 * @author aner
 * @version 1.0
 * @date 2020/10/19 20:51
 */
public class AnerTester {

    private static Logger logger = LoggerUtils.getLogger(AnerTester.class);
    /**
     * the tested controller package path
     */
    private String controllerPackageNames;

    /**
     * custom object package
     */
    private String objectPackageNames;

    /**
     * nested level count of custom object
     */
    private int nestedLevelCount;

    /**
     * the controller names without be tested
     */
    private String excludeControllerNames;

    /**
     * test random data
     */
    private String randomData;

    private List<Class<? extends Annotation>> annotationClassList = new ArrayList<>();

    private Set<String> exclideControllerSet = new HashSet<>();

    public void start() {
        try {
            init();
            doStart();
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    private void init() {

        /**
         * aner tester configuration init
         */
        AnerTesterConfig.me().init();

        /**
         * set configuration information
         */
        controllerPackageNames = AnerTesterConfig.me().getResolverControllerPackage();
        objectPackageNames = AnerTesterConfig.me().getResolverObjectPackage();
        nestedLevelCount = AnerTesterConfig.me().getNestedLevelCount();
        excludeControllerNames = AnerTesterConfig.me().getExcludeController();
        randomData = AnerTesterConfig.me().getRandomData();

        /**
         * set controller annotation type
         */
        annotationClassList.add(RestController.class);
        annotationClassList.add(Controller.class);
        annotationClassList.add(RequestMapping.class);

        /**
         * set nested level count of custom object
         */
        ObjectGenerator.setNestedLevelCount(nestedLevelCount);

        /**
         * set random data
         */
        ValueGenerator.setRandomData(randomData);

        /**
         * type init
         */
        if (StringUtils.isNotEmpty(objectPackageNames)) {
            TypeManager.init(objectPackageNames.split(Constant.PACKAGE_SEPARATOR));
        }
    }

    private void doStart(){

        // generate code
        Future<TaskResult> taskResultFuture = CodeGenExecutor.me().submit(() -> {
            TaskResult taskResult;
            long start = System.currentTimeMillis();
            try {
                logger.info("Scanning for packages...");

                // scan packages
                Set<Class<?>> entitySet = ScannerFactory.scan(annotationClassList,
                        controllerPackageNames.split(Constant.PACKAGE_SEPARATOR), FilterFlag.ONE);
                entitySet = excludeController(entitySet);
                logger.info("Resolving for packages...");

                // resolve controllers of packages
                List<ControllerObject> controllerObjectList = ControllerResolver.resolve(entitySet);

                // generate code file
                new CodeGenerator().generate(controllerObjectList);
                long time = System.currentTimeMillis() - start;
                taskResult = createTaskResult(time, TaskStatus.SUCCESS);
            } catch (Exception e) {
                logger.error(e.getMessage());
                long time = System.currentTimeMillis() - start;
                taskResult = createTaskResult(time, TaskStatus.FAILURE);
            }
            return taskResult;
        });

        doResult(taskResultFuture);

    }

    public TaskResult createTaskResult(long time, TaskStatus taskStatus) {
        TaskResult taskResult = new TaskResult();
        taskResult.setFinishedTime(DateUtils.getTime());
        taskResult.setTotalTime(time);
        taskResult.setTaskStatus(taskStatus);
        return taskResult;
    }

    /**
     * deal with result
     *
     * @param future
     */
    private void doResult(Future<TaskResult> future) {
        try {
            TaskResult taskResult = future.get();
            logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            logger.info(" Build {}", taskResult.getTaskStatus().name());
            logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            logger.info(" Total time:  {} ms", taskResult.getTotalTime());
            logger.info(" Finished at:  {}", taskResult.getFinishedTime());
            logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

            CodeGenExecutor.me().destroy();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    /**
     * exclude controller which are not tested
     *
     * @param entitySet
     * @return
     */
    private Set<Class<?>> excludeController(Set<Class<?>> entitySet) {
        if (StringUtils.isNotEmpty(excludeControllerNames)) {
            exclideControllerSet.addAll(Arrays.stream(excludeControllerNames.split(Constant.PACKAGE_SEPARATOR)).collect(Collectors.toSet()));
            return entitySet.stream().filter(entity -> !exclideControllerSet.contains(entity.getSimpleName())).collect(Collectors.toSet());
        } else {
            return entitySet;
        }

    }


    public String getControllerPackageNames() {
        return controllerPackageNames;
    }

    public void setControllerPackageNames(String controllerPackageNames) {
        this.controllerPackageNames = controllerPackageNames;
    }

    public String getObjectPackageNames() {
        return objectPackageNames;
    }

    public void setObjectPackageNames(String objectPackageNames) {
        this.objectPackageNames = objectPackageNames;
    }

    public int getNestedLevelCount() {
        return nestedLevelCount;
    }

    public void setNestedLevelCount(int nestedLevelCount) {
        this.nestedLevelCount = nestedLevelCount;
    }

    public String getExcludeControllerNames() {
        return excludeControllerNames;
    }

    public void setExcludeControllerNames(String excludeControllerNames) {
        this.excludeControllerNames = excludeControllerNames;
    }

    public String getRandomData() {
        return randomData;
    }

    public void setRandomData(String randomData) {
        this.randomData = randomData;
    }
}

